//
// FauxjoBuilder
//

package net.fauxjobuilder;

import java.io.*;
import java.sql.*;
import java.util.*;
import net.jextra.javatemplar.*;

public class FauxjoBuilder
{
    // ============================================================
    // Fields
    // ============================================================

    public static final String IGNORE = "#IGNORE#";

    private Connection _connection;
    private Map<String,Table> _tables;
    private String _connDef;
    private String _dbSchema;
    private String _package;
    private String _javaSchemaName;
    private File _dir;

    // ============================================================
    // Constructors
    // ============================================================

    public FauxjoBuilder( Connection connection, String connDef, String dbSchema )
    {
        _connection = connection;
        _connDef = connDef;
        _dbSchema = dbSchema;
    }

    // ============================================================
    // Methods
    // ============================================================

    public void getTablesInfo()
        throws Exception
    {
        DatabaseMetaData data = _connection.getMetaData();

        ResultSet tableRS = data.getTables( null, _dbSchema, null, new String[]
        {
            "TABLE"
        } );
        _tables = new LinkedHashMap<String,Table>();
        while ( tableRS.next() )
        {
            Table table = new Table( tableRS.getString( "TABLE_SCHEM" ),
                tableRS.getString( "TABLE_NAME" ), tableRS.getString( "REMARKS" ) );
            getTableInfo( table );
            _tables.put( table.getRawName().toLowerCase(), table );
        }
        tableRS.close();
    }

    public void getTableInfo( Table table )
        throws Exception
    {
        DatabaseMetaData data = _connection.getMetaData();

        //
        // Collect key columns
        //
        HashSet<String> keyColumns = new HashSet<String>();
        ResultSet rs = data.getPrimaryKeys( null, null, table.getRawName() );
        while ( rs.next() )
        {
            keyColumns.add( rs.getString( "COLUMN_NAME" ).toLowerCase() );
        }
        rs.close();

        //
        // Collect field definitions.
        //
        rs = data.getColumns( null, null, table.getRawName(), null );
        while ( rs.next() )
        {
            Column column = new Column( rs.getString( "COLUMN_NAME" ), rs.getString( "REMARKS" ) );
            column.setType( rs.getInt( "DATA_TYPE" ) );
            column.setNullable( rs.getInt( "NULLABLE" ) == 1 );
            column.setPrimaryKey( keyColumns.contains( column.getRawName().toLowerCase() ) );

            table.addColumn( column );
        }
        rs.close();

        //
        // Collect FK definitions.
        //
        rs = data.getImportedKeys( null, null, table.getRawName() );
        while ( rs.next() )
        {
            ForeignKey fk = new ForeignKey( rs.getString( "PKTABLE_SCHEM" ),
                rs.getString( "FKCOLUMN_NAME" ), rs.getString( "PKTABLE_NAME" ),
                rs.getString( "PKCOLUMN_NAME" ) );

            table.addForeignKey( fk );
        }
        rs.close();
    }

    public void print( PrintWriter out )
        throws SQLException
    {
        DatabaseMetaData data = _connection.getMetaData();

        for ( Table table : _tables.values() )
        {
            out.println( table.getBeanName() );

            for ( Column column : table.getColumns() )
            {
                out.println( " " + ( column.isPrimaryKey() ? "*" : "." ) + " " +
                    column.getRawName() + " fieldName=" + column.getFieldName() + " type=" +
                    column.getType() + " nullable=" + column.isNullable() + " => javaType=" +
                    column.getJavaType() );
            }

            ResultSet rs = data.getImportedKeys( null, null, table.getRawName() );
            while ( rs.next() )
            {
                out.println( " & " + rs.getString( "FKCOLUMN_NAME" ) + " " +
                    rs.getString( "PKTABLE_NAME" ) + "." + rs.getString( "PKCOLUMN_NAME" ) + " " +
                    rs.getString( "KEY_SEQ" ) );
            }
            rs.close();
        }
    }

    public void execute( File dir, String pkg, String schemaName, boolean singleton )
        throws Exception
    {
        _dir = dir;
        _package = pkg;
        _javaSchemaName = schemaName;

        for ( Table table : _tables.values() )
        {
            if ( table.getBeanName().equalsIgnoreCase( IGNORE ) )
            {
                continue;
            }

            writeBean( table );
            writeHome( table );
        }

        writeSchema( singleton );
    }

    public void writeBean( Table table )
        throws Exception
    {
        //
        // Write file.
        //
        JavaTemplar templator = new JavaTemplar( new File( "BeanTemplate.txt" ) );

        templator.setVariable( "beanName", table.getBeanName() );
        templator.setVariable( "package", _package );

        for ( Column column : table.getColumns() )
        {
            System.out.println( "column " + table.getBeanName() + "." + column.getFieldName() +
                " " + column.getJavaType() );
            templator.addBlock( "fields" );
            templator.setVariable( "getterAnnotation", column.isPrimaryKey() ? "@FauxjoPrimaryKey" :
                "" );
            templator.setVariable( "javaType", column.getJavaType() );
            templator.setVariable( "fieldName", column.getFieldName() );
            templator.setVariable( "argName", column.getArgName() );
            templator.setVariable( "getterName", column.getGetterName() );
            templator.setVariable( "setterName", column.getSetterName() );
        }

        for ( ForeignKey fk : table.getForeignKeys() )
        {
            // TODO fix this to work with cross schema foreign keys. For now, if the foreign key is
            // in a different schema, just skip it.
            if ( !fk.getForeignSchemaName().equals( table.getSchema() ) )
            {
                continue;
            }

            Table fTable = _tables.get( fk.getForeignTableName().toLowerCase() );
            Column column = table.getColumn( fk.getColumnName() );

            templator.addBlock( "references" );

            templator.setVariable( "ref.javaType", fTable.getBeanName() );
            templator.setVariable( "ref.fieldName", fTable.getFieldName() );
            templator.setVariable( "ref.argName", fTable.getArgName() );
            templator.setVariable( "ref.getterName", fTable.getGetterName() );
            templator.setVariable( "ref.setterName", fTable.getSetterName() );

            templator.setVariable( "ref.idFieldName", column.getFieldName() );
            templator.setVariable( "ref.idGetterName", column.getGetterName() );
        }

        FileWriter out = new FileWriter( new File( _dir, table.getBeanName() + ".java" ) );
        templator.write( out );
        out.close();
    }

    public void writeHome( Table table )
        throws Exception
    {
        //
        // Iterate over primary keys.
        //
        StringBuilder finderName = new StringBuilder();
        StringBuilder args = new StringBuilder();
        StringBuilder whereClause = new StringBuilder();
        ArrayList<String> setters = new ArrayList<String>();
        int setterIndex = 1;
        for ( Column column : table.getColumns() )
        {
            if ( !column.isPrimaryKey() )
            {
                continue;
            }

            //
            // Finder name
            //
            if ( finderName.length() > 0 )
            {
                finderName.append( "and" );
            }
            finderName.append( column.getFinderName() );

            //
            // Args
            //
            if ( args.length() > 0 )
            {
                args.append( ", " );
            }
            args.append( column.getJavaType() );
            args.append( " " );
            args.append( column.getArgName() );

            //
            // Where clause
            //
            if ( whereClause.length() > 0 )
            {
                whereClause.append( " and " );
            }
            whereClause.append( column.getRawName() );
            whereClause.append( "=?" );

            //
            // Setter
            //
            StringBuilder setter = new StringBuilder();
            setter.append( column.getStatementSetter() );
            setter.append( "( " );
            setter.append( setterIndex++ );
            setter.append( ", " );
            setter.append( column.getArgName() );
            setter.append( " )" );

            setters.add( setter.toString() );
        }

        //
        // Write file.
        //
        JavaTemplar templator = new JavaTemplar( new FileReader( "HomeTemplate.txt" ) );

        templator.setVariable( "homeName", table.getHomeName() );
        templator.setVariable( "dbSchema", _dbSchema );
        templator.setVariable( "beanName", table.getBeanName() );
        templator.setVariable( "finderName", finderName.toString() );
        templator.setVariable( "package", _package );
        templator.setVariable( "args", args.toString() );
        templator.setVariable( "whereClause", whereClause.length() == 0 ? "" : "where " +
            whereClause );
        for ( String setter : setters )
        {
            templator.addBlock( "setters" );
            templator.setVariable( "setter", setter );
        }

        FileWriter out = new FileWriter( new File( _dir, table.getHomeName() + ".java" ) );
        templator.write( out );
        out.close();
    }

    public void writeSchema( boolean singleton )
        throws Exception
    {
        //
        // Iterate over primary keys.
        //
        ArrayList<String> beanNames = new ArrayList<String>();
        for ( Table table : _tables.values() )
        {
            if ( table.getBeanName().equalsIgnoreCase( IGNORE ) )
            {
                continue;
            }

            //
            // Bean names
            //
            beanNames.add( table.getBeanName() );
        }

        //
        // Write file.
        //
        JavaTemplar templator = new JavaTemplar( new FileReader( singleton ? 
            "SingletonSchemaTemplate.txt" : "SchemaTemplate.txt" ) );

        templator.setVariable( "package", _package );
        templator.setVariable( "javaSchemaName", _javaSchemaName );
        templator.setVariable( "dbSchemaName", _dbSchema );
        templator.setVariable( "connDef", _connDef );
        for ( String beanName : beanNames )
        {
            templator.addBlock( "beanNames" );
            templator.setVariable( "beanName", beanName );
        }

        FileWriter out = new FileWriter( new File( _dir, _javaSchemaName + ".java" ) );
        templator.write( out );
        out.close();
    }
}

